package bibliotheque.transaction;

import bibliotheque.client.remote.ClientRemoteIS;
import bibliotheque.document.Document;
import bibliotheque.document.DocumentId;
import bibliotheque.document.DocumentSummary;
import bibliotheque.server.Server;
import bibliotheque.wave.WaveId;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.concurrent.Semaphore;

public class TransactionManager {
    
    private class SelectionThread extends Thread {
        
        private Transaction transaction;
        private Document document;
        
        public SelectionThread(Transaction transaction, Document document) {
            this.transaction = transaction;
            this.document = document;
        }
        
        public void run() {
            boolean ok;
            if(this.transaction.GetType() == Server.SelectionType.CONSULT) ok = this.document.Consult(this.transaction.GetId());
            else ok = this.document.Take(this.transaction.GetId());
            
            if(ok) this.transaction.AddDocument(this.document);
        }
    }
    
    
    public enum FreeState {NOTFOUND, SUCCESS, ERROR};
    
    protected Hashtable<TransactionId, Transaction> transactions;
    protected Hashtable<DocumentId, Document> documents;
    protected int LastTransactionId;
    protected int LastDocumentId;
    protected Hashtable<WaveId, FreeState> freewaves;
    protected Hashtable<WaveId, String> readwaves;
    protected Semaphore mutex;
    protected Hashtable<WaveId, Boolean> validateWaves;
    
    public TransactionManager() {
        this.transactions = new Hashtable<TransactionId, Transaction>();
        this.documents = new Hashtable<DocumentId, Document>();
        this.freewaves = new Hashtable<WaveId, FreeState>();
        this.readwaves = new Hashtable<WaveId, String>();
        this.validateWaves = new Hashtable<WaveId, Boolean>();
        
        this.LastTransactionId = 0;
        this.LastDocumentId = 0;
        this.mutex = new Semaphore(1);
    }
    
    public TransactionId GetNewTransactionId(String ip) {
        return(new TransactionId(ip, this.LastTransactionId++));
    }
    
    public DocumentId GetNewDocumentId(String ip) {
        return(new DocumentId(ip, this.LastDocumentId++));
    }
    
    public void SetDocuments(Hashtable<DocumentId, Document> documents) {
        try{
            this.mutex.acquireUninterruptibly();
            this.documents = documents;
        } finally {
            this.mutex.release();
        }
    }
    
    public boolean AddDocument(String title, String summary, String content, String ip){
        try {
            this.mutex.acquireUninterruptibly();
            DocumentId docId = this.GetNewDocumentId(ip);
            this.documents.put(docId,new Document(docId,title,summary,content));
            return true;
            //else return false;
        } finally {
            this.mutex.release();
        }
    }
    
    public boolean FreeDocument(TransactionId tid, DocumentId did)  throws Exception {
        try{
            this.mutex.acquireUninterruptibly();
            Document doc = this.documents.get(did);
            if (doc == null)
                return false;
            doc.Free(tid);
            this.transactions.get(tid).RemoveDocument(this.documents.get(did));
            return true;
        } finally {
            this.mutex.release();
        }
        
    }
    
    public FreeState FreeInit(WaveId wid, TransactionId tid, DocumentId did)  {
        FreeState free_state = null;
        try{
            boolean res = this.FreeDocument(tid, did);
            if(res) free_state = FreeState.SUCCESS;
            else free_state = FreeState.NOTFOUND;
        } catch (Exception ex) {
            free_state = FreeState.ERROR;
        } finally {
            this.mutex.acquireUninterruptibly();
            this.freewaves.put(wid, free_state);
            this.mutex.release();
            return(free_state);
        }
    }
    
    public void FreeUpdate(WaveId wid, FreeState free_state) {
        try{
            this.mutex.acquireUninterruptibly();
            if(free_state != FreeState.NOTFOUND) this.freewaves.put(wid,free_state);
        } finally {
            this.mutex.release();
        }
    }
    
    public FreeState FreeResult(WaveId wid) {
        try{
            this.mutex.acquireUninterruptibly();
            FreeState free_state = this.freewaves.remove(wid);
            if(free_state == null) free_state = FreeState.NOTFOUND;
            return(free_state);
        } finally {
            this.mutex.release();
        }
    }
    
    public String GetDocumentContent(TransactionId tid, DocumentId did) throws Exception {
        try{
            this.mutex.acquireUninterruptibly();
            Document doc = this.documents.get(did);
            if (doc == null) return null;
            else return(doc.GetContents(tid));
        } finally {
            this.mutex.release();
        }
    }
    
    public String ReadInit(WaveId wid, TransactionId tid, DocumentId did) {
        String content = null;
        try{
            content = this.GetDocumentContent(tid, did);
        } catch (Exception ex){
            
        } finally {
            
            this.mutex.acquireUninterruptibly();
            if(content != null) this.readwaves.put(wid, content);
            this.mutex.release();
            return content;
        }
    }
    
    public void ReadUpdate(WaveId wid, String content) {
        try{
            this.mutex.acquireUninterruptibly();
            if(content != null) this.readwaves.put(wid,content);
        } finally {
            this.mutex.release();
        }
    }
    
    public String ReadResult(WaveId wid) {
        try{
            this.mutex.acquireUninterruptibly();
            return(this.readwaves.remove(wid));
        } finally {
            this.mutex.release();
        }
    }
    
    public void ResolvInit(TransactionId tid, ArrayList<String> filtre, Server.SelectionType type) throws Exception {
        try{
            this.mutex.acquireUninterruptibly();
            boolean resolv = true;
            Transaction t1,t2;
            Enumeration<Transaction> enumeration;
            if(this.transactions.containsKey(tid)) {
                t1 = this.transactions.get(tid);
            } else {
                t1 = new Transaction(tid, filtre, type);
                this.transactions.put(tid, t1);
            }
            
            if (t1.GetState() == Transaction.State.RESOLVED) throw new Exception("ResolvInit failed cause state is resolved.");
            if (t1.GetState() == Transaction.State.SELECTED) throw new Exception("ResolvInit failed cause state is selected.");
            if (t1.GetState() == Transaction.State.SELECTING) throw new Exception("ResolvInit failed cause state is selecting.");
            
            enumeration = this.transactions.elements();
            while(resolv && enumeration.hasMoreElements()) {
                t2 = enumeration.nextElement();
                if(!t1.equals(t2)) resolv = t1.ResolvPrior(t2);
            }
            t1.SetResolv(resolv);
            t1.SetResolvNb(t1.GetResolvNb() + 1);
        } finally {
            this.mutex.release();
        }
    }
    
    public boolean ResolvUpdate(TransactionId tid, boolean resolv) throws Exception {
        try{
            this.mutex.acquireUninterruptibly();
            Transaction t = this.transactions.get(tid);
            if(t == null) throw new Exception("ResolvUpdate failed cause the transactionID does not exist.");
            if (t.GetState() == Transaction.State.RESOLVED) throw new Exception("ResolvUpdate failed cause state is resolved.");
            if (t.GetState() == Transaction.State.SELECTED) throw new Exception("ResolvUpdate failed cause state is selected.");
            if (t.GetState() == Transaction.State.SELECTING) throw new Exception("ResolvUpdate failed cause state is selecting.");
            t.SetResolv(t.GetResolv() && resolv);
            return(t.GetResolv());
        } finally {
            this.mutex.release();
        }
    }
    
    public boolean ResolvResult(TransactionId tid) throws Exception {
        try{
            this.mutex.acquireUninterruptibly();
            Transaction t = this.transactions.get(tid);
            if(t == null) throw new Exception("ResolvFinish failed cause the transactionID does not exist.");
            if (t.GetState() == Transaction.State.RESOLVED) throw new Exception("ResolvFinish failed cause state is resolved.");
            if (t.GetState() == Transaction.State.SELECTED) throw new Exception("ResolvFinish failed cause state is selected.");
            if (t.GetState() == Transaction.State.SELECTING) throw new Exception("ResolvFinish failed cause state is selecting.");
            if(t.GetResolv()) t.SetState(Transaction.State.RESOLVED);
            return(t.GetResolv());
        } finally {
            this.mutex.release();
        }
    }
    
    public void PreSelectInit(TransactionId tid) throws Exception {
        try {
            this.mutex.acquireUninterruptibly();
            final Transaction t = this.transactions.get(tid);
            if(t == null) throw new Exception("SelectInit failed cause the transactionID does not exist.");
            if (t.GetState() == Transaction.State.RESOLVING) throw new Exception("PreSelectInit failed cause transaction state is resolving.");
            if (t.GetState() == Transaction.State.SELECTED) throw new Exception("PreSelectInit failed cause transaction state is selected.");
            if (t.GetState() == Transaction.State.RESOLVING) throw new Exception("PreSelectInit failed cause transaction state is resolving.");
            
            t.AcquireSelectMutex();
            
            Thread thread = new Thread() {
                public void run() {
                    Document doc;
                    SelectionThread thread;
                    Enumeration<Document> enumeration;
                    ArrayList<Thread> threads = new ArrayList<Thread>();
                    
                    enumeration = documents.elements();
                    while (enumeration.hasMoreElements()) {
                        doc = enumeration.nextElement();
                        if(doc.MatchFilter(t.GetFiltre())) {
                            thread = new SelectionThread(t, doc);
                            thread.start();
                            threads.add(thread);
                        }
                    }
                    
                    try {
                        for(int i=0;i<threads.size();i++) threads.get(i).join();
                    } catch (Exception ex) {
                        
                    }
                    
                    t.ReleaseSelectMutex();
                }
            };
            thread.start();
            
        } finally {
            this.mutex.release();
        }
    }
    
    public void SelectInit(TransactionId tid) throws Exception {
        try {
            this.mutex.acquireUninterruptibly();
            final Transaction t = this.transactions.get(tid);
            if(t == null) throw new Exception("SelectInit failed cause the transactionID does not exist.");
            if (t.GetState() == Transaction.State.RESOLVING) throw new Exception("SelectInit failed cause state is resolving.");
            if (t.GetState() == Transaction.State.SELECTING) throw new Exception("SelectInit failed cause state is selecting.");
            if (t.GetState() == Transaction.State.SELECTED) throw new Exception("SelectInit failed cause transaction state is selected.");
            
            t.SetState(Transaction.State.SELECTING);
        } finally {
            this.mutex.release();
        }
    }
    
    public void SelectUpdate(TransactionId tid, ArrayList<DocumentSummary> summaries) throws Exception {
        try {
            this.mutex.acquireUninterruptibly();
            Transaction t = this.transactions.get(tid);
            if(t == null) throw new Exception("SelectUpdate failed cause the transactionID does not exist.");
            if (t.GetState() == Transaction.State.RESOLVING) throw new Exception("SelectUpdate failed cause transaction state is resolving.");
            if (t.GetState() == Transaction.State.RESOLVED) throw new Exception("SelectUpdate failed cause transaction state is resolved.");
            if (t.GetState() == Transaction.State.SELECTED) throw new Exception("SelectUpdate failed cause transaction state is selected.");
            
            t.AddSummaries(summaries);
        } finally {
            this.mutex.release();
        }
    }
    
    public ArrayList<DocumentSummary> SelectResult(TransactionId tid) throws Exception {
        try {
            this.mutex.acquireUninterruptibly();
            Transaction t = this.transactions.get(tid);
            if(t == null) throw new Exception("SelectResult failed cause the transactionID does not exist.");
            this.mutex.release();
            try {
                t.AcquireSelectMutex();
                if (t.GetState() == Transaction.State.RESOLVING) throw new Exception("SelectResult failed cause transaction state is resolving.");
                if (t.GetState() == Transaction.State.RESOLVED) throw new Exception("SelectResult failed cause transaction state is resolved.");
                if (t.GetState() == Transaction.State.SELECTED) throw new Exception("SelectResult failed cause transaction state is selected.");
                
                return(t.GetSummaries());
            } finally {
                t.ReleaseSelectMutex();
                this.mutex.acquireUninterruptibly();
            }
        } finally {
            this.mutex.release();
        }
    }
    
    public void SelectResponse(TransactionId tid, ClientRemoteIS client) throws Exception {
        try {
            this.mutex.acquireUninterruptibly();
            Transaction t = this.transactions.get(tid);
            if(t == null) throw new Exception("SelectResponse failed cause the transactionID does not exist.");
            if (t.GetState() == Transaction.State.RESOLVING) throw new Exception("SelectResponse failed cause transaction state is resolving.");
            if (t.GetState() == Transaction.State.RESOLVED) throw new Exception("SelectResponse failed cause transaction state is resolved.");
            if (t.GetState() == Transaction.State.SELECTING) throw new Exception("SelectResponse failed cause transaction state is selecting.");
            
            if(t.GetType() == Server.SelectionType.CONSULT) client.SetConsultResult(t.GetSummaries(), tid);
            else client.SetTakeResult(t.GetSummaries(), tid);
        } catch (RemoteException ex){
            //System.out.println(ex.getMessage());
        } finally {
            this.mutex.release();
        }
    }
    
    public void Validate(TransactionId tid) throws Exception {
        try {
            this.mutex.acquireUninterruptibly();
            Transaction t = this.transactions.get(tid);
            ArrayList<Document> doc;
            if (t == null) throw new Exception("Validate failed because the transaction does not exist.");
            doc = t.GetDocuments();
            if (t.GetState() == Transaction.State.RESOLVING) throw new Exception("Validate failed because transaction is resolving");
            if (t.GetState() == Transaction.State.SELECTING) throw new Exception("Validate failed because transaction is selecting");
            if (t.GetType() == Server.SelectionType.WITHDRAW) {
                for (int i = 0; i < doc.size(); i++){
                    DocumentId did = doc.get(i).GetDocSummary().GetId();
                    if (this.documents.containsKey(did)){
                        this.documents.get(did).FreeAndRemove(tid);
                        this.documents.remove(did);
                    }
                }
            }
            if (t.GetType() == Server.SelectionType.CONSULT){
                for (int i = 0; i < doc.size(); i++){
                    DocumentId did = doc.get(i).GetDocSummary().GetId();
                    if (this.documents.containsKey(did)){
                        this.documents.get(did).Free(tid);
                    }
                }
            }
            this.transactions.remove(tid);
        }finally{
            this.mutex.release();
        }
        
    }
    
    public void Invalidate(TransactionId tid) throws Exception{
        try {
            this.mutex.acquireUninterruptibly();
            Transaction t = this.transactions.get(tid);
            ArrayList<Document> doc;
            if (t == null) throw new Exception("Invalidate failed because the transaction does not exist.");
            doc = t.GetDocuments();
            if (t.GetState() == Transaction.State.RESOLVING) throw new Exception("Invalidate failed because transaction is resolving");
            if (t.GetState() == Transaction.State.SELECTING) throw new Exception("Invalidate failed because transaction is selecting");
            for (int i = 0; i < doc.size(); i++){
                DocumentId did = doc.get(i).GetDocSummary().GetId();
                if (this.documents.containsKey(did)){
                    try {
                        this.documents.get(did).Free(tid);
                    } catch (Exception ex) {
                        
                    }
                }
            }
            //this.transactions.remove(tid);
        } finally{
            this.mutex.release();
        }
        
    }
    
    public Server.SelectionType GetTransactionType(TransactionId tid){
        try {
            return this.transactions.get(tid).GetType();
        } catch(Exception ex) {
            return null;
        }
    }
    
    public void PrevalidateInit(TransactionId tid, WaveId wid) throws Exception{
        try {
            this.mutex.acquireUninterruptibly();
            Transaction t = this.transactions.get(tid);
            ArrayList<Document> doc;
            if (t == null) throw new Exception("PreValidateInit failed because the transaction does not exist.");
            doc = t.GetDocuments();
            if (t.GetState() == Transaction.State.RESOLVING) throw new Exception("PreValidateInit failed because transaction is resolving");
            if (t.GetState() == Transaction.State.SELECTING) throw new Exception("PreValidateInit failed because transaction is selecting");
            if (t.GetType() == Server.SelectionType.WITHDRAW) {
                boolean b = true;
                for (int i = 0; i < doc.size(); i++){
                    DocumentId did = doc.get(i).GetDocSummary().GetId();
                    if (this.documents.containsKey(did)){
                        try {
                            this.documents.get(did).PreFreeAndRemove(tid);
                        }catch (Exception ex){
                            b = false;
                        }
                    }
                }
                this.validateWaves.put(wid,b);
            }
        }finally{
            this.mutex.release();
        }
    }
    
    
    public void PreValidateUpdate(WaveId wid, boolean b) throws Exception{
        
        try {
            this.mutex.acquireUninterruptibly();
            this.validateWaves.put(wid, this.validateWaves.get(wid) && b);
            
        }finally{
            this.mutex.release();
        }
    }
    
    
    
    public boolean PreValidateResult(WaveId wid){
        return this.validateWaves.get(wid);
    }
    
}

